package sandbox;

import helpers.d;
import soenudp.Command;
import soenudp.UDPHelper;
import soenudp.UDPMessage;
import java.util.Arrays;
import helpers.CustomHash;
import soenudp.UDPException;

/**
 * @author Davis - 5715822
 * Use this with FakeMTLGroup.java  
 * To have the list of connected nodes 
 * in a Group.
 */
public class UDPViewGMS 
{
  private static final Object RECV_LOCK = new Object();
  // Multicast Address, different from than IP.
  final static String MTL_MULTICAST_ADR   = "235.255.0.1";
  final static int    MTL_MULTICAST_PORT  = 9200;
 
  final static String TOR_MULTICAST_ADR   = "235.255.0.2";
  final static int    TOR_MULTICAST_PORT  = 9100;

  public static void main(String[] args)
  {      
    d.out("Starting Test...\nDon't forget to run FakeMTLGroup at the smae time.");
    String MTL_View = "";
    String TOR_View = "";
    UDPHelper groupHelper = new UDPHelper();
    
    // Start the receiver thread. It will handle a unicast socket.
    UDPViewGMSRecv receiverThread = new UDPViewGMSRecv(MTL_View, TOR_View);
    receiverThread.start(); 
  
    try 
    {  
       // This will shutdown a node when it's equal to '15'
       // It will make her leave the group, thus changing
       // the view and the bsd_hash.
      int nodeShutdownTest = 0; 
      int PreviousMTLHash  = 0;
      
      UDPMessage BPing = new UDPMessage();
      BPing.command = Command.BPING;
      
      UDPHelper loop_test = new UDPHelper();
      loop_test.startUnicastClient("test");
      UDPMessage m = new UDPMessage(null, null, Command.CANCEL, 0, 10, null);
      for(int i=1; i<256; i++) {
        m.transac=i;
        loop_test.sendToNode("127.0.0.1", 5252, m);
      }
      loop_test.stopUnicast();
     // UDPMessage m = new UDPMessage(null, null, Command.CANCEL, 0, 2000, null);

      groupHelper.joinGroup(MTL_MULTICAST_ADR, MTL_MULTICAST_PORT, true, "GMS001");
      for (int i=0; i<200; i++){
       m.transac = i;
       groupHelper.sendToGroup(m);
       
      }
      groupHelper.leaveGroup();


   //   for(int i=0; i<1000; i++) {     
    //    UDPMessage m = new UDPMessage(null, null, Command.ERROR, 0, i, null);
     //   loop_test.sendToNode("127.0.0.1", 9927, m);
      //  groupHelper.sendToGroup(m);
      //}
     // groupHelper.leaveGroup();
      
      groupHelper.joinGroup(MTL_MULTICAST_ADR, MTL_MULTICAST_PORT, true, "GMS001");
      while (true) 
      { 
               
        d.out("\nSending: " + BPing.command);
        d.out("---------------------------");
        
        groupHelper.sendToGroup(BPing);
        d.sleep(3 * 1000); // Give them time to reply back. (or timeout)
        
        // Create a hash value of the View to check for changes
        int MTL_Hash = 0;
        MTL_View = getMTLView(receiverThread);
        
        String[] MTL_Sorted = MTL_View.split("\n");
        Arrays.sort(MTL_Sorted); // Order is important
        for (String s : MTL_Sorted) MTL_Hash += CustomHash.bsd_hash(s);
        
        d.out("VIEW: \n" + MTL_View);  
        d.out("HASH: " + MTL_Hash); 
        
        // For example, let's say we want to shutdown MTL002 from
        // The view. (Make sure you have the FakeMTLGroup Running)
        if (nodeShutdownTest == 3)
        {
          String MTL002IP = getIP("MTL002" , MTL_View);
          int MTL002Port = getPort("MTL002", MTL_View);
          shutDown(MTL002IP, MTL002Port);
        }
        
        // Check if the network as changed by looking at the hash
        if (PreviousMTLHash !=0 &&( PreviousMTLHash != MTL_Hash )) 
        {
          d.out("^^^^^^^^^^^ Ooops! Wrong bsd_hash, someone's missing!\n\n");
          break;
        }
        // Start a blank view for next iteration
        clearAllViews(receiverThread);
        nodeShutdownTest++;
        PreviousMTLHash = MTL_Hash;
      } 
        
      groupHelper.leaveGroup();
      receiverThread.interrupt();
      receiverThread.join();
      listAllThreads();
     
      
    } catch (UDPException e)
    {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } 
  }
  
  // Retrieve the IP Adddress of a NodeID in the View
  private static String getIP(String NodeID, String theView)
  {
    String[] nodesArr = theView.split("\n");
    for (String nodeRow : nodesArr)
    {
      String[] nodeRecord = nodeRow.split(":");
      if (nodeRecord[0].equals(NodeID)) return nodeRecord[1];
    }  
    return null; // NodeID is not in the View!
  }
  
  // Retrieve the port of a NodeID in the View
  private static int getPort(String NodeID, String theView)
  {
    String[] nodesArr = theView.split("\n");
    for (String nodeRow : nodesArr)
    {
      String[] nodeRecord = nodeRow.split(":");
      if (nodeRecord[0].equals(NodeID)) return Integer.valueOf(nodeRecord[2]);
    }  
    return -1; // NodeID is not in the VIEW
  }
  
  // ShutDown a Node
  private static void shutDown(String IPAdr, int port)
  {
    try 
    {  
      UDPHelper quick = new UDPHelper();
      quick.startUnicastClient("911");
      //One of the simplest command. (leaveGroup)
      UDPMessage shutDown = new UDPMessage();
      shutDown.command = Command.SHUTDOWN;
      quick.sendToNode(IPAdr, port, shutDown);
      quick.stopUnicast();
    } 
    catch (UDPException e)
    {
      e.printStackTrace();
    }
  }
  
  private static String getMTLView(UDPViewGMSRecv receiverThread)
  {
    synchronized (RECV_LOCK) 
    {
        return receiverThread.getMTLView();
    }
  }
 
  private static void clearAllViews(UDPViewGMSRecv receiverThread)
  {
    synchronized (RECV_LOCK) 
    {
      receiverThread.clearAllViews();
    }
  }
  
  private static void printThreadInfo(Thread t, String indent) {
      if (t == null)
        return;
      System.out.println(indent + "Thread: " + t.getName() + "  Priority: "
          + t.getPriority() + (t.isDaemon() ? " Daemon" : "")
          + (t.isAlive() ? "" : " Not Alive"));
    }

    /** Display info about a thread group */
    private static void printGroupInfo(ThreadGroup g, String indent) {
      if (g == null)
        return;
      int numThreads = g.activeCount();
      int numGroups = g.activeGroupCount();
      Thread[] threads = new Thread[numThreads];
      ThreadGroup[] groups = new ThreadGroup[numGroups];

      g.enumerate(threads, false);
      g.enumerate(groups, false);

      System.out.println(indent + "Thread Group: " + g.getName()
          + "  Max Priority: " + g.getMaxPriority()
          + (g.isDaemon() ? " Daemon" : ""));

      for (int i = 0; i < numThreads; i++)
        printThreadInfo(threads[i], indent + "    ");
      for (int i = 0; i < numGroups; i++)
        printGroupInfo(groups[i], indent + "    ");
    }

    /** Find the root thread group and list it recursively */
    public static void listAllThreads() {
      ThreadGroup currentThreadGroup;
      ThreadGroup rootThreadGroup;
      ThreadGroup parent;

      // Get the current thread group
      currentThreadGroup = Thread.currentThread().getThreadGroup();

      // Now go find the root thread group
      rootThreadGroup = currentThreadGroup;
      parent = rootThreadGroup.getParent();
      while (parent != null) {
        rootThreadGroup = parent;
        parent = parent.getParent();
      }

      printGroupInfo(rootThreadGroup, "");
    }
    
}
